﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Data;
using Wildmatch;

namespace DbView
{
	class DbViewDataGridView : DataGridView
	{
		public DbViewDataGridView()
		{
			AutoSizeMaximumWidth = 700;		 // a reasonable default. Clients can override
			BigTableCellCount = (1500 * 20); // compatibility default. Clients can override
			this.m_HoldSelectionBackColour = this.DefaultCellStyle.SelectionBackColor;
		}

		// above this value (pixels I think) columns will not be autosized
		// see constructor for a sensible default
		public int AutoSizeMaximumWidth { get; set; }

		// if the table cell count is above this value then auto-sizing will be disabled
		// causing a faster display. See constructor for a sensible default.
		public int BigTableCellCount { get; set; }

		// warning isModifiable is redundant
		public void BindDataTable(DataTable sourceTable, bool isModifiable)
		{
			this.DataSource = null;
			// Stop now if the table parameter is null
			if (sourceTable == null)
				return;
			//BindingSource to sync DataTable and DataGridView
			BindingSource bSource = new BindingSource();

			// test size of table
			bool isBigTable = (sourceTable.Rows.Count * sourceTable.Columns.Count > BigTableCellCount) ? true : false;
			// compress unless it would be potentially slow
			if (!isBigTable) TrimData(sourceTable);

			// set the BindingSource DataSource
			bSource.DataSource = sourceTable;
			//    // set the DataGridView DataSource
			this.DataSource = bSource;
			// unconditional set to readonly
			this.ReadOnly = true;

			// set whether potentially modifiable
			this.m_Modifiable = isModifiable;
			// use extra info instead: this allows for complete pkey presence detection
			DataTableExtraInfoManager mgr = new DataTableExtraInfoManager(sourceTable);
			this.m_Modifiable = mgr.IsModifiable();
			// todo: trim the data
			// todo: pretty up the data

			// resize cells to data depends on the size of the grid
			// 1500 * 20 = >30,000 cells to format then don't
			if (isBigTable)
				this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
			else
			{
				this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
				// reset really wide columns
				foreach (DataGridViewColumn dgvc in this.Columns)
				{
					if (dgvc.Width > AutoSizeMaximumWidth) // HM.
					{
						dgvc.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
						dgvc.Width = AutoSizeMaximumWidth;
					}
				}
			}
			// set auto sort
			foreach (DataColumn dc in sourceTable.Columns)
			{
				int sortFlag = mgr.IsSorted(dc);
				if (sortFlag != 0)
				{
					System.ComponentModel.ListSortDirection sortDir = (sortFlag == -1) ? System.ComponentModel.ListSortDirection.Descending
																						: System.ComponentModel.ListSortDirection.Ascending;
					this.Sort(this.Columns[dc.ColumnName], sortDir);
				}
			}
		}

		// try this 
		public void UnBindDataTable()
		{
			this.DataSource = null;
		}

		public DataTable GridDataTable
		{
			get
			{
				BindingSource bSource = (BindingSource)this.DataSource;
				DataTable table = (bSource == null) ? null : (DataTable)bSource.DataSource;
				return table;
			}
		}

		public string DataTableName
		{
			get
			{
				DataTable gt = GridDataTable;
				return (gt == null) ? null : gt.TableName;
			}
		}

		public bool Modifiable
		{
			get { return this.m_Modifiable; }
		}

		public int DataGridWidth
		{
			get
			{
				int width = this.RowHeadersWidth;
				foreach (DataGridViewColumn dgvc in Columns)
				{
					if (dgvc.Visible)
					{
						width += dgvc.Width;
					}
				}
				return width;
			}
		}

		// todo: add interface to set meta data in datatable for 
		// pkeys and fkeys or add a metadata on demand object somehow.
		public int GetSelectedRow(out DataRow selectedRow)
		{
			// DataGridViewRow relates to DataGridView
			DataGridViewRow current = this.CurrentRow;
			if (current == null)
			{
				selectedRow = null;
				return 0;
			}
			// this is related to a view of the data via the DataBoundItem prop.
			DataRowView rv = (DataRowView)current.DataBoundItem;
			// rv is null if the row is the empty edit row,
			if (rv == null)
			{
				selectedRow = null;
				return 0;
			}
			selectedRow = rv.Row;
			BindingSource bSource = (BindingSource)this.DataSource;
			DataTable dt = (DataTable)bSource.DataSource;
			for (int idx2 = 0; idx2 < dt.Rows.Count; ++idx2)
				if (dt.Rows[idx2] == selectedRow)
					return idx2;
			return 0;
		}

		public void SelectAllRows()
		{
			foreach (DataGridViewRow row in this.Rows)
				row.Selected = true;
		}

		// sets a value in the data table for the current sort
		public string MarkSortColumn()
		{
			DataGridViewColumn dgvcs = this.SortedColumn;
			if (dgvcs == null) return ""; // nothing to do
			DataTable dt = GridDataTable;
			DataColumn dc = dt.Columns[dgvcs.DataPropertyName];
			DataTableExtraInfoManager mgr = new DataTableExtraInfoManager(dt);
			mgr.SetSorted(dc, (this.SortOrder == System.Windows.Forms.SortOrder.Descending) ? true : false);
			return ((this.SortOrder == System.Windows.Forms.SortOrder.Descending) ? "-" : "+") + dc.ColumnName;
		}

		public void ResizeRowsToSelectedRowHeight()
		{
			// actually make expand to largest.
			DataGridViewRow current = this.CurrentRow;
			if (current == null) return; // what to do?
			int targetHeight = current.Height;
			this.SuspendLayout();
			DataGridViewAutoSizeColumnsMode holdMode = this.AutoSizeColumnsMode;
			this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
			foreach (DataGridViewRow row in this.Rows)
				row.Height = targetHeight;
			this.AutoSizeColumnsMode = holdMode;
			this.ResumeLayout();
		}

		public GridCellInfo GetCurrentCell()
		{
			if (this.CurrentCell == null)
				return null;
			return new GridCellInfo(this.CurrentCell);
		}

		public bool SetModifiable(bool modifiable)
		{
			// can't change a non-modifiable table
			if (!this.Modifiable) modifiable = false;
			// modifiable = !readonly
			ReadOnly = !modifiable;
			// set new/delete permission
			AllowUserToAddRows = modifiable;
			AllowUserToDeleteRows = modifiable;
			this.DefaultCellStyle.SelectionBackColor = (modifiable) ? System.Drawing.Color.Firebrick : this.m_HoldSelectionBackColour;
			return modifiable;
		}

		public void DecorateTable(bool fixPkeys, bool colourKeys)
		{
			DataTableExtraInfoManager mgr = new DataTableExtraInfoManager(GridDataTable);
			int freeze = -1;
			int idx = 0;
			foreach (DataGridViewColumn col in this.Columns)
			{
				string fieldName = col.HeaderText;
				DataColumn dc = GridDataTable.Columns[fieldName];
				if (dc != null)
				{
					if (mgr.IsPkey(dc))
					{
						col.DefaultCellStyle.BackColor = System.Drawing.Color.Bisque;
						if (idx == freeze + 1)
						{
							col.Frozen = true;
							++freeze;
						}
					}
					else if (mgr.IsFkey(dc)) // fkey
					{
						col.DefaultCellStyle.BackColor = System.Drawing.Color.Tan;
					}
					else if (!string.IsNullOrEmpty(mgr.GetLookupKeyField(dc)))
					{
						col.DefaultCellStyle.BackColor = System.Drawing.Color.LightSkyBlue;
					}
					else if (mgr.IsIndexed(dc)) // index
					{
						col.DefaultCellStyle.BackColor = System.Drawing.Color.PaleGoldenrod;
					}
					++idx;
				}
			}
			// Row colours
			String[] colours = mgr.GetRowColours();
			// only do colll of this if there is something to do
			if (colours.GetLength(0) > 0)
			{
				// create a map to look-up the gridview row from the datarow
				// this because the colour information is set in datatable row index terms
				// but needs to be applied in DataGridViewRow terms
				Dictionary<int, int> mapRowtoGridRow = new Dictionary<int, int>();
				// 
				int dgvrIdx = 0;	// gridview row index
				int drIdx = 0;		// datatable row index
				foreach (DataGridViewRow dgvr in this.Rows)
				{
					DataRowView drv = dgvr.DataBoundItem as DataRowView; // crawl to the DataRow
					DataRow dr = drv.Row;								 // from the gridviewrow
					drIdx = dr.Table.Rows.IndexOf(dr);					 // Ouch! get the index from a lookup (not too expensive I hope)
					mapRowtoGridRow.Add(drIdx, dgvrIdx);				 // to update the map 
					++dgvrIdx;
				}

				foreach (String colour in colours)
				{
					System.Drawing.Color c = System.Drawing.Color.FromName(colour);
					// hack: look for diffs in following row if blue
					bool bDiffColour = (c == System.Drawing.Color.Blue);
					// hack: look for diffs in following row if blue
					int[] rows = mgr.GetColourRows(colour);

					foreach (int row in rows)
					{
						if (mapRowtoGridRow.ContainsKey(row))
						{
							int dgvr = mapRowtoGridRow[row];
							this.Rows[dgvr].DefaultCellStyle.ForeColor = c;
							// hack: look for diffs in following row if blue
							if (bDiffColour && dgvr < this.Rows.Count - 1)  // Quickdiff only
							{
								ColourCellDiffs(this.Rows[dgvr], this.Rows[dgvr + 1]);
							}
							// hack: look for diffs in following row if blue
						}
					}
				}
			}

		}

		// hack: look for diffs in following row if blue
		private void ColourCellDiffs(DataGridViewRow srcData, DataGridViewRow tgtData)
		{
			// more hacking. Suppress this if fewer than 4 columns
			if (srcData.Cells.Count < 4) return;
			// so only really for field differences.
			// 1st column of diff is < >
			for (int cellIdx = 1; cellIdx < srcData.Cells.Count; ++cellIdx)
			{
				object src = srcData.Cells[cellIdx].Value;
				object tgt = tgtData.Cells[cellIdx].Value;
				if (!src.Equals(tgt))
				{
					if (IsEmpty(src))
					{
						tgtData.Cells[cellIdx].Style.BackColor = System.Drawing.Color.LightCoral;
						srcData.Cells[cellIdx].Style.BackColor = System.Drawing.Color.LightGray;
					}
					else
					{
						srcData.Cells[cellIdx].Style.BackColor = (IsEmpty(tgt)) ? System.Drawing.Color.LawnGreen :
																					System.Drawing.Color.LightBlue;
						tgtData.Cells[cellIdx].Style.BackColor = System.Drawing.Color.LightGray;
					}
				}
			}
		}

		bool IsEmpty(object o)
		{
			return (o.ToString().Length == 0);
		}
		// hack: look for diffs in following row if blue


		// check returned dset and trim wide char colunms with blank spaces
		// to squeeze more data into view.
		public void TrimData(DataTable dt)
		{
			// search for string columns.
			foreach (DataColumn dc in dt.Columns)
			{
				if (dc.DataType == typeof(String))
				{
					// don't bother to process data less than the field name length
					int namel = dc.ColumnName.Length;
					foreach (DataRow dr in dt.Rows)
					{
						string source = dr[dc].ToString();
						if (source.Length > namel) // field name length check
							if (source[source.Length - 1] == ' ')  // something to do check
								dr[dc] = dr[dc].ToString().Trim(); // replace only where necessary
					}
				}
			}
			// have to spike the changes so that they are not written to the database
			dt.AcceptChanges();
		}


		public void ApplyFilter(String filterString)
		{
			this.CurrentCell = null; //otherwise below may complain.
			// case-insensitive matching 
			string match = filterString.ToUpper();
			foreach (DataGridViewRow dgr in this.Rows)
			{
				// "" filter = reset to show all
				bool matches = (match.Length == 0); // T if zero string ie always match
				if (!matches)
				{
					foreach (DataGridViewCell cell in dgr.Cells)
					{
						string data = cell.Value.ToString().ToUpper();
						if (data.IndexOf(match) != -1)
						{
							matches = true;
							break;
						}
					}
				}
				dgr.Visible = matches;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////
		// This is for marking in highlighter the rows that match the search text
		// 
		public void ApplyRegexFilter(String patternIn, int minEffectivePatternLength)
		{
			// test minimum effective pattern length and clear it if it is below that
			string pattern = (patternIn.Length < minEffectivePatternLength) ? "" : patternIn;

			System.Drawing.Color mcb = this.m_MatchColor;
			SimpleRegex reg = new SimpleRegex(pattern);
			bool firstMatch = true;
			bool cellChange = false;
//			this.CurrentCell = null; //otherwise below may complain.
			foreach (DataGridViewRow dgr in this.Rows)
			{
				foreach (DataGridViewCell cell in dgr.Cells)
				{
					// Normally setting/keeping at this colour
					System.Drawing.Color cc = cell.OwningColumn.DefaultCellStyle.BackColor;
					// above ok if no pattern matching
					if (pattern.Length > 0)
					{
						string data = cell.Value.ToString();
						// set to the match colour if there is a match
						if (reg.IsMatch(data))
						{
							cc = mcb;
							if (firstMatch == true)
							{
								this.FirstDisplayedScrollingRowIndex = cell.OwningRow.Index;
								this.CurrentCell = cell;
								firstMatch = false;
							}
						}
					}
					if (cell.Style.BackColor != cc)
					{
						cell.Style.BackColor = cc;
						cellChange = true;   // useful to know whether we have changed anything
					}
				}
			}
			// reset the cell if it wasn't above, but if a marked cell was unmarked.
			if (firstMatch == true && this.RowCount > 0 && cellChange) 
			{
				this.FirstDisplayedScrollingRowIndex = 0;   // only reset if there was a change
				this.CurrentCell = this.Rows[0].Cells[0];
			}
		}
		
		internal int CountMarked()
		{
			int count = 0;
			foreach (DataGridViewRow dgr in this.Rows)
			{
				foreach (DataGridViewCell cell in dgr.Cells)
				{
					if (cell.Style.BackColor == this.m_MatchColor)
						++count;
				}
			}
			return count;
		}

		// Handling navigation of matching text
		// -1 = up 1 = down. Stay put if none found
		// cycle
		public void MoveCurrentSelection(int direction)
		{
			if (direction < 0)
				MoveCurrentToPreviousSelection();
			else
				MoveCurrentToNextSelection();
		}

		// Search forward cycling round when at the end
		private void MoveCurrentToNextSelection()
		{
			// set the start cell and end row
			int startRow = (this.CurrentCell == null) ? 0 : this.CurrentCell.RowIndex;
			int startColumn = (this.CurrentCell == null) ? 0 : this.CurrentCell.ColumnIndex + 1;
			int endRow = this.RowCount;
			// two passes to implement cycling round ie start at the beginning when you reach the end
			for (int loop = 0; loop < 2; ++loop)
			{
				for (int i = startRow; i < endRow; ++i)
				{
					for (int j = startColumn; j < this.ColumnCount; ++j)
					{
						// match on the highlight colour
						if (this[j, i].Style.BackColor == this.m_MatchColor)
						{
							this.CurrentCell = this[j, i];
							return; // enough. job done.
						}
					}
					// after the firsr row reset to start the search at the 1st column
					startColumn = 0;
				}
				// OK - nothing found searching forward. Search from the start 
				endRow = startRow + 1; // 0 -> 1-based I think. Halt when you reach where you started.
				startRow = 0;		   // start from top row.  startColumn already set to 0
			}
		}

		private void MoveCurrentToPreviousSelection()
		{
			int startRow = (this.CurrentCell == null) ? this.RowCount - 1: this.CurrentCell.RowIndex;
			int startColumn = (this.CurrentCell == null) ? this.ColumnCount : this.CurrentCell.ColumnIndex - 1;
			int endRow = 0;
			// two passes to implement cycling round ie start at the beginning when you reach the end
			for (int loop = 0; loop < 2; ++loop)
			{
				for (int i = startRow; i > endRow; --i)
				{
					for (int j = startColumn - 1 ; j >= 0; --j)
					{
						if (this[j, i].Style.BackColor == this.m_MatchColor)
						{
							this.CurrentCell = this[j, i];
							return;
						}
					}
					startColumn = this.ColumnCount;
				}
				// OK - nothing found searching forward. Search from the start 
				endRow = startRow;				// and startColumn already at end
				startRow = this.RowCount - 1; // 0 -> 1-based I think
			}
		}

		System.Drawing.Color m_MatchColor = System.Drawing.Color.FromArgb(255, 255, 96);
		bool m_Modifiable = false;
		System.Drawing.Color m_HoldSelectionBackColour;


	}

	class GridCellInfo
	{
		public GridCellInfo(DataGridViewCell cell)
		{
			DataGridView dgv = cell.DataGridView;
			this.m_ColumnName = dgv.Columns[cell.ColumnIndex].Name;
			this.m_DataType = dgv.Columns[cell.ColumnIndex].ValueType;
			this.m_Data = cell.Value.ToString();
		}

		public string Data { get { return this.m_Data; } }
		public string ColumnName { get { return this.m_ColumnName; } }
		public System.Type DataType { get { return this.m_DataType; } }

		string m_Data;
		string m_ColumnName;
		System.Type m_DataType;
	}
}
